package com.jet.core.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.jet.core.annotation.RepeatSubmitLock;
import com.jet.core.constant.CommonConstant;
import com.jet.core.enums.RepeatSubmitStrategy;
import com.jet.core.result.ResultCode;
import com.jet.core.result.ResultJson;
import com.jet.core.utils.SpelUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author dcl
 * @since 2024/12/23 18:08
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
    // 重复提交锁逻辑
    // 提交key = 请求方式大写+所有请求参数按字母顺序排序(key=value&key=value)后md5加密

    private final RedissonClient redissonClient;

    public RepeatSubmitAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("@annotation(repeatSubmitLock)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmitLock repeatSubmitLock) throws Throwable {
        String key = getLockKey(joinPoint, repeatSubmitLock);
        if (StrUtil.isBlank(key)) {
            return ResultJson.failure(ResultCode.BAD_REQUEST);
        }
        RLock lock = redissonClient.getLock(key);
        boolean isLocked = false;
        try {
            // 尝试获取锁，等待waitTime毫秒，持有锁时长leaseTime毫秒
            isLocked = lock.tryLock(repeatSubmitLock.waitTime(), repeatSubmitLock.leaseTime(), repeatSubmitLock.timeUnit());
            if (!isLocked) {
                return ResultJson.failure(ResultCode.REPEAT_REQUEST);
            }
            return joinPoint.proceed();
        } finally {
            // 方法体执行完后进行解锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static String getLockKey(ProceedingJoinPoint joinPoint, RepeatSubmitLock repeatSubmitLock) {
        String result = null;
        if (repeatSubmitLock.strategy() == RepeatSubmitStrategy.ALL_FIELD) {
            result = getAllFieldResult(joinPoint);
        } else if (repeatSubmitLock.strategy() == RepeatSubmitStrategy.SPEL) {
            result = getSpelResult(joinPoint, repeatSubmitLock.key());
        } else {
            result = getIpResult(joinPoint);
        }
        if (result == null) {
            return null;
        }
        log.info("lock key:{}", result);
        return CommonConstant.REPEAT_SUBMIT_LOCK_PREFIX + result;
    }

    private static String getIpResult(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return null;
        }
        HttpServletRequest request = attributes.getRequest();
        String requestUrl = request.getRequestURL().toString();
        String ipAddress = request.getRemoteAddr();
        return requestUrl +
                StrUtil.UNDERLINE +
                ipAddress;
    }

    private static String getAllFieldResult(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        final Object[] args = joinPoint.getArgs();
        final Parameter[] parameters = method.getParameters();
        SortedMap<String, Object> map = new TreeMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Object arg = args[i];
            Parameter parameter = parameters[i];
            map.put(parameter.getName(), arg);
        }
        String params = JSONUtil.toJsonStr(map);
        return SecureUtil.md5(params);
    }

    /**
     * 将spel表达式转换为字符串
     *
     * @param joinPoint 切点
     * @return redisKey
     */
    private static String getSpelResult(ProceedingJoinPoint joinPoint, String spel) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Object target = joinPoint.getTarget();
        Object[] arguments = joinPoint.getArgs();
        return StrUtil.COLON + SpelUtil.parse(target, spel, targetMethod, arguments);
    }
}
